﻿using System;
using System.ComponentModel;
using System.Windows.Input;

namespace Creventive.Wpf.ShellFactory.Workspace
{
    /// <summary>
    /// Represents a command to be used in view models.
    /// </summary>
    public class ModelCommand : ICommand, INotifyPropertyChanged
    {
        private bool isEnabled = true;
        private EventHandler canExecuteChanged;
        private Action<object> executeCallback;
        private Func<object, bool> executeQueryQueryCallback;

        /// <summary>
        /// Initializes a new instance of the <see cref="ModelCommand"/> class.
        /// </summary>
        public ModelCommand()
        {}

        /// <summary>
        /// Initializes a new instance of the <see cref="ModelCommand"/> class.
        /// </summary>
        /// <param name="isEnabled">if set to <c>true</c>, this command is enabled by default.</param>
        public ModelCommand(bool isEnabled)
        {
            this.IsEnabled = isEnabled;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ModelCommand"/> class.
        /// </summary>
        /// <param name="executeCallback">The execute callback.</param>
        /// <param name="isEnabled">if set to <c>true</c>, this command is enabled by default.</param>
        public ModelCommand(Action executeCallback, bool isEnabled = true)
            : this(isEnabled)
        {
            this.executeCallback = p => executeCallback.Invoke();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ModelCommand"/> class.
        /// </summary>
        /// <param name="executeCallback">The execute callback.</param>
        /// <param name="executeQueryCallback">The execute query callback.</param>
        /// <param name="isEnabled">if set to <c>true</c>, this command is enabled by default.</param>
        public ModelCommand(Action executeCallback, Func<bool> executeQueryCallback, bool isEnabled = true)
            : this(isEnabled)
        {
            this.executeCallback = p => executeCallback.Invoke();
            this.executeQueryQueryCallback = p => executeQueryCallback.Invoke();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ModelCommand"/> class.
        /// </summary>
        /// <param name="executeCallback">The execute callback.</param>
        /// <param name="isEnabled">if set to <c>true</c>, this command is enabled by default.</param>
        public ModelCommand(Action<object> executeCallback, bool isEnabled = true)
            : this(isEnabled)
        {
            this.executeCallback = executeCallback;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ModelCommand"/> class.
        /// </summary>
        /// <param name="executeCallback">The execute callback.</param>
        /// <param name="executeQueryCallback">The execute query callback.</param>
        /// <param name="isEnabled">if set to <c>true</c>, this command is enabled by default.</param>
        public ModelCommand(Action<object> executeCallback, Func<object, bool> executeQueryCallback, bool isEnabled = true)
            : this(isEnabled)
        {
            this.executeCallback = executeCallback;
            this.executeQueryQueryCallback = executeQueryCallback;
        }

        event EventHandler ICommand.CanExecuteChanged
        {
            add { this.canExecuteChanged += value; }
            remove { this.canExecuteChanged -= value; }
        }

        /// <summary>
        /// Occurs when the command is asked if execution is possible.
        /// </summary>
        public event EventHandler<ModelCommandExecuteQueriedEventArgs> ExecuteQueried;
        /// <summary>
        /// Occurs when the command has been executed.
        /// </summary>
        public event EventHandler<ModelCommandExecutedEventArgs> Executed;

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Gets or sets a value indicating whether this command is enabled.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this command is enabled; otherwise, <c>false</c>.
        /// </value>
        public bool IsEnabled
        {
            get { return this.isEnabled; }
            set
            {
                if (this.isEnabled == value)
                    return;
                this.isEnabled = value;
                this.OnIsEnabledChanged();
            }
        }

        /// <summary>
        /// Called when the IsEnabled property has changed.
        /// </summary>
        protected virtual void OnIsEnabledChanged()
        {
            this.OnPropertyChanged("IsEnabled");
            var handler = this.canExecuteChanged;
            if (handler != null)
                handler.Invoke(this, EventArgs.Empty);
        }

        /// <summary>
        /// Executes the command.
        /// </summary>
        /// <param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
        public virtual void Execute(object parameter = null)
        {
            if (this.CanExecute(parameter))
            {
                var eventArgs = new ModelCommandExecutedEventArgs(parameter);
                if (this.executeCallback != null)
                    this.executeCallback.Invoke(eventArgs.Parameter);
                var handler = this.Executed;
                if (handler != null)
                    handler.Invoke(this, eventArgs);
            }
        }

        /// <summary>
        /// Determines whether this command can be executed.
        /// </summary>
        /// <param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
        /// <returns>
        /// true if this command can be executed; otherwise, false.
        /// </returns>
        public virtual bool CanExecute(object parameter = null)
        {
            if (!this.isEnabled)
                return false;

            var eventArgs = new ModelCommandExecuteQueriedEventArgs(parameter);
            if (this.executeQueryQueryCallback != null)
            {
                this.executeQueryQueryCallback.Invoke(eventArgs.Parameter);
                if (eventArgs.Cancel)
                    return false;
            }
            var handler = this.ExecuteQueried;
            if (handler != null)
                handler.Invoke(this, eventArgs);
            return !eventArgs.Cancel;
        }

        void ICommand.Execute(object parameter)
        {
            this.Execute(parameter);
        }

        bool ICommand.CanExecute(object parameter)
        {
            return this.CanExecute(parameter);
        }

        /// <summary>
        /// Called when a property has been changed.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            var handler = this.PropertyChanged;
            if (handler != null)
                handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    /// <summary>
    /// Represents a command to be used in view models.
    /// </summary>
    /// <typeparam name="Targ">The type of the command parameter.</typeparam>
    public class ModelCommand<Targ> : INotifyPropertyChanged, ICommand<Targ>
    {
        private bool isEnabled = true;
        private EventHandler canExecuteChanged;
        private Action<Targ> executeCallback;
        private Func<Targ, bool> executeQueryQueryCallback;

        /// <summary>
        /// Initializes a new instance of the <see cref="ModelCommand&lt;Targ&gt;"/> class.
        /// </summary>
        public ModelCommand()
        {}

        /// <summary>
        /// Initializes a new instance of the <see cref="ModelCommand&lt;Targ&gt;"/> class.
        /// </summary>
        /// <param name="isEnabled">if set to <c>true</c>, this command is enabled by default.</param>
        public ModelCommand(bool isEnabled)
        {
            this.IsEnabled = isEnabled;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ModelCommand&lt;Targ&gt;"/> class.
        /// </summary>
        /// <param name="executeCallback">The execute callback.</param>
        /// <param name="isEnabled">if set to <c>true</c>, this command is enabled by default.</param>
        public ModelCommand(Action<Targ> executeCallback, bool isEnabled = true)
            : this(isEnabled)
        {
            this.executeCallback = executeCallback;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ModelCommand&lt;Targ&gt;"/> class.
        /// </summary>
        /// <param name="executeCallback">The execute callback.</param>
        /// <param name="executeQueryCallback">The execute query callback.</param>
        /// <param name="isEnabled">if set to <c>true</c>, this command is enabled by default.</param>
        public ModelCommand(Action<Targ> executeCallback, Func<Targ, bool> executeQueryCallback, bool isEnabled = true)
            : this(isEnabled)
        {
            this.executeCallback = executeCallback;
            this.executeQueryQueryCallback = executeQueryCallback;
        }

        event EventHandler ICommand.CanExecuteChanged
        {
            add { this.canExecuteChanged += value; }
            remove { this.canExecuteChanged -= value; }
        }

        /// <summary>
        /// Occurs when the command is asked if execution is possible.
        /// </summary>
        public event EventHandler<ModelCommandExecuteQueriedEventArgs<Targ>> ExecuteQueried;
        /// <summary>
        /// Occurs when the command has been executed.
        /// </summary>
        public event EventHandler<ModelCommandExecutedEventArgs<Targ>> Executed;
        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Gets or sets a value indicating whether this command is enabled.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this command is enabled; otherwise, <c>false</c>.
        /// </value>
        public bool IsEnabled
        {
            get { return this.isEnabled; }
            set
            {
                if (this.isEnabled == value)
                    return;
                this.isEnabled = value;
                this.OnIsEnabledChanged();
            }
        }

        /// <summary>
        /// Called when the IsEnabled property has changed.
        /// </summary>
        protected virtual void OnIsEnabledChanged()
        {
            this.OnPropertyChanged("IsEnabled");
            var handler = this.canExecuteChanged;
            if (handler != null)
                handler.Invoke(this, EventArgs.Empty);
        }

        /// <summary>
        /// Executes the command.
        /// </summary>
        /// <param name="parameter">Data used by the command.  If the command does not require data to be passed, this argument can be omitted.</param>
        public virtual void Execute(Targ parameter = default(Targ))
        {
            if (this.CanExecute(parameter))
            {
                var eventArgs = new ModelCommandExecutedEventArgs<Targ>(parameter);
                if (this.executeCallback != null)
                    this.executeCallback.Invoke(eventArgs.Parameter);
                var handler = this.Executed;
                if (handler != null)
                    handler.Invoke(this, eventArgs);
            }
        }

        /// <summary>
        /// Determines whether this command can be executed.
        /// </summary>
        /// <param name="parameter">Data used by the command.  If the command does not require data to be passed, this argument can be omitted.</param>
        /// <returns>
        /// true if this command can be executed; otherwise, false.
        /// </returns>
        public virtual bool CanExecute(Targ parameter = default(Targ))
        {
            if (!this.isEnabled)
                return false;

            var eventArgs = new ModelCommandExecuteQueriedEventArgs<Targ>(parameter);
            if (this.executeQueryQueryCallback != null)
            {
                this.executeQueryQueryCallback.Invoke(eventArgs.Parameter);
                if (eventArgs.Cancel)
                    return false;
            }
            var handler = this.ExecuteQueried;
            if (handler != null)
                handler.Invoke(this, eventArgs);
            return !eventArgs.Cancel;
        }

        void ICommand.Execute(object parameter)
        {
            this.Execute((Targ)parameter);
        }

        bool ICommand.CanExecute(object parameter)
        {
            return this.CanExecute((Targ)parameter);
        }

        /// <summary>
        /// Called when a property has been changed.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            var handler = this.PropertyChanged;
            if (handler != null)
                handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}